package fts.ftsmonitoring.initializer;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import fts.ftsmonitoring.ftsTaggers.IAutoTagger;
import fts.ftsdata.services.ITaggingService;
import fts.ftsmonitoring.FileMonitoringRoot;
import fts.ftsmonitoring.InitializationStatus;
import fts.ftsmonitoring.Utils;

public class FTSInitializer
{

	private static class FTSInitializerST
	{

		private static FTSInitializer initObj;
		static
		{
			initObj = new FTSInitializer();
		}

		static FTSInitializer getInstance()
		{
			return initObj;
		}
	}

	private HashMap<Class<?>, IAutoTagger> taggers = new HashMap<Class<?>, IAutoTagger>();
	private ArrayList<IInitializerObserver> observers = new ArrayList<IInitializerObserver>();

	public static FTSInitializer getInstance()
	{
		return FTSInitializerST.getInstance();
	}

	public synchronized void registerInitializerObserver(IInitializerObserver observer)
	{
		System.out.println(Thread.currentThread().getStackTrace()[1]);

		observers.add(observer);
	}

	public synchronized void unregisterInitializerObserver(IInitializerObserver observer)
	{
		System.out.println(Thread.currentThread().getStackTrace()[1]);

		observers.remove(observer);
	}

	private synchronized void notifyStatusChange()
	{
		System.out.println(Thread.currentThread().getStackTrace()[1]);

		for (IInitializerObserver observer : observers)
		{
			try
			{
				observer.onInitializerStatusChange(getStatus());
			}
			catch (Exception ex)
			{

			}
		}
	}

	private synchronized void notifyProgressChange()
	{
		System.out.println(Thread.currentThread().getStackTrace()[1]);

		for (IInitializerObserver observer : observers)
		{
			try
			{
				observer.onInitializerProgressChange(progress.get());
			}
			catch (Exception ex)
			{

			}
		}
	}

	public synchronized void addTagger(IAutoTagger tagger)
	{
		if (this.taggers.containsKey(tagger.getClass()))
		{
			this.taggers.remove(tagger.getClass());
		}
		this.taggers.put(tagger.getClass(), tagger);
	}

	public synchronized void removeTagger(IAutoTagger tagger)
	{
		this.removeTagger(tagger.getClass());
	}

	public synchronized void removeTagger(Class<?> taggerClass)
	{
		if (this.taggers.containsKey(taggerClass))
		{
			this.taggers.remove(taggerClass);
		}
	}
	
	public synchronized void applyTaggers(String fileName, ITaggingService taggingService)
	{
		Utils.applyTaggersMap(fileName, taggingService, taggers);
	}

	private InitializationStatus status = InitializationStatus.IDLE;
	private AtomicBoolean shouldStop = new AtomicBoolean(false);
	private AtomicInteger progress = new AtomicInteger(0);

	public void Stop()
	{
		if (getStatus() == InitializationStatus.RUNNING)
		{
			shouldStop.set(true);
			notifyStatusChange();
		}
	}

	private synchronized void changeStatus(InitializationStatus newStatus)
	{
		status = newStatus;
		notifyStatusChange();
	}

	public synchronized InitializationStatus getStatus()
	{
		if (status == InitializationStatus.RUNNING && shouldStop.get())
		{
			return InitializationStatus.STOPPING;
		}
		return status;
	}

	public int getProgress()
	{
		return progress.get();
	}

	public void runAsync(Collection<FileMonitoringRoot> roots, ITaggingService fact)
	{
		if (getStatus() == InitializationStatus.IDLE)
		{
			shouldStop.set(false);
			progress.set(0);
			notifyProgressChange();
			changeStatus(InitializationStatus.RUNNING);

			InitializerThread thread = new InitializerThread(roots, fact);
			thread.start();
			// TODO: implement run
		}
	}

	private class InitializerThread extends Thread
	{

		ArrayList<FileMonitoringRoot> roots = new ArrayList<FileMonitoringRoot>();
		ITaggingService taggingService;

		InitializerThread(Collection<FileMonitoringRoot> roots, ITaggingService service)
		{
			this.roots.addAll(roots);
			this.taggingService = service;
		}

		HashMap<Path, String> pathToPatterns = new HashMap<Path, String>();
		ArrayList<String> filesToTag = new ArrayList<String>();

		@Override
		public void run()
		{
			try
			{
				taggingService.deleteAutoTags();
				Iterator<FileMonitoringRoot> rootsIter = roots.iterator();
				pathToPatterns = new HashMap<Path, String>();
				filesToTag = new ArrayList<String>();
				while (rootsIter.hasNext())
				{
					FileMonitoringRoot root = rootsIter.next();
					File _dir = new File(root.getRootPath());

					if (_dir.exists() && _dir.isDirectory())
					{
						Path directotyToWatch = _dir.toPath();

						if (!pathToPatterns.containsKey(directotyToWatch))
						{
							pathToPatterns.put(directotyToWatch, root.getFilePatternsExpression());
						}

						Files.walkFileTree(directotyToWatch, new FileVisitor<Path>()
						{

							public FileVisitResult postVisitDirectory(Path arg0, IOException arg1)
							{
								// TODO Auto-generated method stub
								return shouldStop.get() ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE;
							}

							public FileVisitResult preVisitDirectory(Path arg0)
							{
								// TODO Auto-generated method stub
								return shouldStop.get() ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE;
							}

							public FileVisitResult preVisitDirectoryFailed(Path arg0, IOException arg1)
							{
								// TODO Auto-generated method stub
								return shouldStop.get() ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE;
							}

							public FileVisitResult visitFile(Path arg0, BasicFileAttributes arg1)
							{
								Set<Path> paths = pathToPatterns.keySet();
								Iterator<Path> iter = paths.iterator();
								Path root = null;
								int level = 0;
								while (iter.hasNext())
								{
									Path curr = iter.next();
									int currLvl = curr.getNameCount();
									if (arg0.startsWith(curr) && currLvl >= level)
									{
										level = currLvl;
										root = curr;
									}
								}
								if (root != null)
								{

									if (arg0.toString().matches(pathToPatterns.get(root)))
									{
										filesToTag.add(arg0.toString());
									}
								}
								return shouldStop.get() ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE;
							}

							public FileVisitResult visitFileFailed(Path arg0, IOException arg1)
							{
								// TODO Auto-generated method stub
								return shouldStop.get() ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE;
							}
						});

					}
				}

				int total = filesToTag.size();
				int current = 0;
				for (String fileName : filesToTag)
				{
					if (!shouldStop.get())
					{
						current++;
						applyTaggers(fileName, taggingService);
						progress.set(Math.round((float) current / (float) total * 100));
						notifyProgressChange();
						System.out.println("Progress: " + progress.get() + " -- File: " + fileName);
					}
				}
			}
			catch (Exception ex)
			{
				ex.printStackTrace();
			}
			finally
			{
				changeStatus(InitializationStatus.IDLE);
			}
		}
	}
}
